package com.study.example.gameserver.module.scene.service;

import com.sparrow.common.aoi.domain.AOIEntity;
import com.sparrow.common.aoi.domain.Area;
import com.sparrow.common.aoi.domain.EntityType;
import com.sparrow.common.aoi.listener.AOIListener;
import com.sparrow.common.aoi.util.MapUtil;
import com.sparrow.common.thread.IdExecutorGroup;
import com.sparrow.common.thread.ThreadPool;
import com.sparrow.common.util.CollectionUtil;
import com.sparrow.common.util.JsonUtil;
import com.sparrow.common.util.RandomUtil;
import com.sparrow.common.uuid.TimeUUIDGenerator;
import com.sparrow.event.annotation.EventReceiver;
import com.sparrow.example_message.map.req.ReqAttackHitPacket;
import com.sparrow.example_message.map.req.ReqAttackPacket;
import com.sparrow.example_message.map.req.ReqJumpPacket;
import com.sparrow.example_message.map.req.ReqMovePacket;
import com.sparrow.example_message.map.resp.*;
import com.sparrow.example_message.map.vo.*;
import com.sparrow.resource.cache.ResourceCache;
import com.sparrow.common.map.Position;
import com.study.example.gameserver.config.MonsterResource;
import com.study.example.gameserver.config.SceneResource;
import com.study.example.gameserver.module.attr.domain.AttrType;
import com.study.example.gameserver.module.backpack.service.BackPackService;
import com.study.example.gameserver.module.scene.entity.IEntity;
import com.study.example.gameserver.module.scene.entity.IPlayerEntity;
import com.study.example.gameserver.module.scene.domain.SceneMap;
import com.study.example.gameserver.module.scene.entity.impl.DropItem;
import com.study.example.gameserver.module.scene.entity.impl.Monster;
import com.study.example.gameserver.module.scene.manager.SceneManager;
import com.study.example.gameserver.module.player.entity.Player;
import com.study.example.gameserver.module.player.entity.PlayerEntity;
import com.study.example.gameserver.module.player.event.PlayerLogoutEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Component
public class SceneService implements AOIListener<SceneMap> {

    private static Logger logger = LoggerFactory.getLogger(SceneService.class);
    @Autowired
    private ResourceCache<Integer, SceneResource> sceneResCache;
    @Autowired
    private SceneManager sceneManager;
    @Autowired
    private ResourceCache<Integer, MonsterResource> monsterResCache;
    @Autowired
    private TimeUUIDGenerator uuidGenerator;
    @Autowired
    private IdExecutorGroup idExecutorGroup;
    @Autowired
    private BackPackService backPackService;


    public void enterMap(SceneMap map, IPlayerEntity player) {
        player.setSceneId(map.getSceneId());
        player.setMapId(map.getMapId());
        player.setLineId(map.getLineId());
        map.addPlayer(player);
        logger.info("player:{} enterMap sceneId:{} mapId:{}", player.getPlayerName(), map.getSceneId(), map.getMapId());
    }

    /**
     * 加载地图数据,前端场景初始化时加载地图上的玩家怪物等地图数据
     * @param map
     * @param player
     */
    public void loadMap(SceneMap map, IPlayerEntity player) {
        enterMap(map, player);
        RespLoadMapPacket packet = new RespLoadMapPacket();
        packet.setX((int) player.getPos().getX());
        packet.setY((int) player.getPos().getY());
        packet.setZ((int) player.getPos().getZ());
        packet.setAngle(player.getAngle());

        Collection<AOIEntity> aoiEntities = map.getAoiSystem().queryAOIEntity(map, (int) player.getPos().getX(), (int) player.getPos().getZ());
        for (AOIEntity ee : aoiEntities) {
            if (ee.getEntityType().equals(EntityType.PLAYER)) {
                if (ee.getAOIId() == player.getPlayerId()) {
                    continue;
                }
                IPlayerEntity p = (IPlayerEntity) ee;
                SceneRoleVO other = toSceneRoleVo(p);
                packet.getRoles().add(other);
            } else if (ee.getEntityType().equals(EntityType.MONSTER)) {
                Monster monster = (Monster) ee;
                MonsterVO monsterVO = toMonsterVO(monster);
                packet.getMonsters().add(monsterVO);
            } else if (ee.getEntityType().equals(EntityType.DROP_ITEM)) {
                DropItem dropItem = (DropItem) ee;
                packet.getDropItems().add(toDropItemVo(dropItem));
            }
        }
        player.sendPacket(packet);
        logger.info("player:{} loadMap sceneId:{} mapId:{} packet:{}", player.getPlayerName(), map.getSceneId(), map.getMapId(), JsonUtil.toJSON(packet));
    }

    private MonsterVO toMonsterVO(Monster monster) {
        MonsterVO vo = new MonsterVO();
        vo.setMonsterId(monster.getId());
        vo.setAngle(monster.getAngle());
        vo.setPos(toPositionVO(monster.getPos()));
        vo.setSpeed(monster.getSpeed());
        vo.setHp(monster.getHp());
        vo.setMaxHp(monster.getMaxHp());
        if (CollectionUtil.isNotEmpty(monster.getRoads())) {
            List<PositionVo> roads = new ArrayList<>(monster.getRoads().size());
            for (Position pos : monster.getRoads()) {
                roads.add(toPositionVO(pos));
            }
            vo.setRoads(roads);
            vo.setRoadIndex(monster.getRoadIndex());
        }
        vo.setConfigId(monster.getConfigId());
        return vo;
    }

    private DropItemVo toDropItemVo(DropItem monster) {
        DropItemVo vo = new DropItemVo();
        vo.setId(monster.getId());
        vo.setAngle(monster.getAngle());
        PositionVo pos = new PositionVo();
        pos.setX((int) (monster.getPos().getX() * 10000));
        pos.setY((int) (monster.getPos().getY() * 10000));
        pos.setZ((int) (monster.getPos().getZ() * 10000));
        vo.setPos(pos);
        return vo;
    }

    private Set<Area> getRoundAreas(SceneMap map, IEntity entity) {
        Set<Area> roundArea = MapUtil.getRoundAreaByGrid(map, (int) entity.getPos().getX(), (int) entity.getPos().getZ());
        return roundArea;
    }

    public void move(SceneMap sceneMap, Player player, ReqMovePacket req) {
        int x = req.getX() / 100;
        int y = req.getY() / 100;
        int z = req.getZ() / 100;
        int angle = req.getAngle() / 100;
        if (req.getState() == 1) {
            startMove(sceneMap, player, x, y, z, angle);
        } else if (req.getState() == 3) {
            stopMove(sceneMap, player, x, y, z, angle);
        } else {
            move(sceneMap, player, x, y, z, angle);
        }

        RespPlayerMovePacket movePacket = new RespPlayerMovePacket();
        movePacket.setPlayerId(player.getPlayerId());
        movePacket.setState(req.getState());
        movePacket.setX(req.getX());
        movePacket.setY(req.getY());
        movePacket.setZ(req.getZ());
        movePacket.setAngle(req.getAngle());

        walkRoundArea(sceneMap, player, (area -> {
            Map<Long, AOIEntity> map = area.getEntityMap().get(EntityType.PLAYER);
            if (map != null) {
                for (AOIEntity e : map.values()) {
                    Player p = (Player) e;
                    p.sendPacket(movePacket);
                }
            }
        }));
    }

    public void walkRoundArea(SceneMap sceneMap, IPlayerEntity player, Consumer<Area> consumer) {
        getRoundAreas(sceneMap, player).forEach(consumer);
    }

    public void walkRoundPlayer(SceneMap sceneMap, IEntity entity, Consumer<IPlayerEntity> consumer) {
        Set<Area> roundAreas = getRoundAreas(sceneMap, entity);
        for (Area area : roundAreas) {
            Map<Long, AOIEntity> players = area.getEntityMap().get(EntityType.PLAYER);
            if (players != null && !players.isEmpty()) {
                for (AOIEntity e : players.values()) {
                    IPlayerEntity p = (IPlayerEntity) e;
                    consumer.accept(p);
                }
            }
        }
    }

    public void startMove(SceneMap sceneMap, Player player, float x, float y, float z, int angle) {
        updatePos(sceneMap, player, x, y, z, angle);
    }

    private static void updatePos(SceneMap sceneMap, Player player, float x, float y, float z, int angle) {
        PlayerEntity playerBaseInfo = player.getEntity();
        Position pos = playerBaseInfo.getPos();
        pos.setX(x);
        pos.setY(y);
        pos.setZ(z);
        playerBaseInfo.setAngle(angle);
        playerBaseInfo.update();

        sceneMap.getAoiSystem().updateAOIEntityPosition(sceneMap, player, (int)pos.getX(), (int)pos.getZ());
    }

    public void stopMove(SceneMap sceneMap, Player player, float x, float y, float z, int angle) {
        updatePos(sceneMap, player, x, y, z, angle);
    }

    public void move(SceneMap sceneMap, Player player, float x, float y, float z, int angle) {
        updatePos(sceneMap, player, x, y, z, angle);
    }

    @Override
    public void onEntityLeaveView(SceneMap mapData, AOIEntity entity, AOIEntity target) {
//        logger.info("aoi system onEntityLeaveView entity:{} target:{}", entity.getAOIId(), target.getAOIId());
        if (!entity.getEntityType().equals(EntityType.PLAYER)) {
            return;
        }
        Player player = (Player) entity;
        RespPlayerRemovePacket packet = new RespPlayerRemovePacket();
        packet.setEntityType(target.getEntityType().value());
        packet.setRemoveId(target.getAOIId());
        player.sendPacket(packet);
    }

    @Override
    public void onEntityEnterView(SceneMap mapData, AOIEntity entity, AOIEntity target) {
//        logger.info("aoi system onEntityEnterView entity:{} target:{}", entity.getAOIId(), target.getAOIId());
        if (entity.getEntityType().equals(EntityType.PLAYER)) {
            Player player = (Player) entity;
            if (target.getEntityType().equals(EntityType.PLAYER)) {
                Player p = (Player) target;
                RespEntityAppearPacket packet = new RespEntityAppearPacket();
                packet.getRoles().add(toSceneRoleVo(p));
                player.sendPacket(packet);
            } else if (target.getEntityType().equals(EntityType.MONSTER)) {
                Monster monster = (Monster) target;
                RespEntityAppearPacket packet = new RespEntityAppearPacket();
                packet.getMonsters().add(toMonsterVO(monster));
                player.sendPacket(packet);
            } else if (target.getEntityType().equals(EntityType.DROP_ITEM)) {
                DropItem monster = (DropItem) target;
                RespEntityAppearPacket packet = new RespEntityAppearPacket();
                packet.getDropItems().add(toDropItemVo(monster));
                player.sendPacket(packet);
            }
        }
    }

    private SceneRoleVO toSceneRoleVo(IPlayerEntity p) {
        SceneRoleVO vo = new SceneRoleVO();
        vo.setLevel(p.getLevel());
        vo.setName(p.getPlayerName());
        vo.setPlayerId(p.getPlayerId());
        vo.setPos(toPositionVO(p.getPos()));
        return vo;
    }

    public PositionVo toPositionVO(Position pos) {
        PositionVo vo = new PositionVo();
        vo.setX((int) pos.getX());
        vo.setY((int) pos.getY());
        vo.setZ((int) pos.getZ());
        return vo;
    }

    @Override
    public void onEntityPositionChange(SceneMap mapData, AOIEntity entity, AOIEntity target) {
//        logger.info("aoi system onEntityPositionChange entity:{} target:{}", entity.getAOIId(), target.getAOIId());
    }

    public void exitMap(SceneMap map, Player player) {
        map.removeEntity(player);
        logger.info("exit map player{}", player.getPlayerId());
    }

    private SceneMap getSceneMap(Player player) {
        return sceneManager.getSceneMap(player.getSceneId(), player.getLineId());
    }

    @EventReceiver
    public void logout(PlayerLogoutEvent event) {
        Player player = event.getPlayer();
        SceneMap map = getSceneMap(player);
        map.tell(() -> {
            exitMap(map, player);
        });
    }

    public void jump(SceneMap sceneMap, IPlayerEntity player, ReqJumpPacket req) {
        RespJumpPacket packet = new RespJumpPacket();
        packet.setPlayerId(player.getPlayerId());
        packet.setX(req.getX());
        packet.setY(req.getY());
        packet.setZ(req.getZ());
        packet.setAngle(req.getAngle());

        walkRoundArea(sceneMap, player, (area -> {
            Map<Long, AOIEntity> map = area.getEntityMap().get(EntityType.PLAYER);
            if (map != null) {
                for (AOIEntity e : map.values()) {
                    if (e.getAOIId() != player.getPlayerId()) {
                        Player p = (Player) e;
                        p.sendPacket(packet);
                    }
                }
            }
        }));
    }

    public void sendMonsterInfo(Monster monster) {
        RespMonsterInfoacket packet = new RespMonsterInfoacket();
        packet.getMonsters().add(toMonsterVO(monster));
        SceneMap sceneMap = sceneManager.getSceneMap(monster.getMapId());
        walkRoundPlayer(sceneMap, monster, (p) -> {
            p.sendPacket(packet);
        });
    }

    public void attack(SceneMap sceneMap, Player player, ReqAttackPacket req) {
        RespAttackPacket packet = new RespAttackPacket();
        packet.setPos(toPositionVO(player.getPos()));
        packet.setPlayerId(player.getPlayerId());
        walkRoundPlayer(sceneMap, player, p -> p.sendPacket(packet));
    }

    public void attackHit(SceneMap sceneMap, Player player, ReqAttackHitPacket req) {
        RespAttackHitPacket packet = new RespAttackHitPacket();
        if (CollectionUtil.isNotEmpty(req.getMonsterIds())) {
            for (Long id : req.getMonsterIds()) {
                Monster monster = (Monster) sceneMap.getMonsters().get(id);
                DamageVo vo = attackMonster(sceneMap, player, monster, packet);
                packet.getDamageList().add(vo);
            }
        }

        if (CollectionUtil.isNotEmpty(packet.getDamageList())) {
            walkRoundPlayer(sceneMap, player, p -> p.sendPacket(packet));
        }
    }

    private DamageVo attackMonster(SceneMap sceneMap, Player attacker, Monster defence, RespAttackHitPacket packet) {
        int atk = (int) attacker.getAttrBox().getFinalAttr().get(AttrType.Atk);
        int dmg = Math.max(1, (atk - defence.getDef()));
        int finalDmg = defence.takeDamage(dmg);
        //记录攻击我得玩家id
        defence.getAttackMeIds().put(attacker.getPlayerId(), System.currentTimeMillis());

        DamageVo vo = new DamageVo();
        vo.setEntityType(EntityType.MONSTER.value());
        vo.setTargetId(defence.getId());
        vo.setDamage(finalDmg);
        if (!defence.isAlive()) {
            killMonster(sceneMap, attacker, defence);
        }
        return vo;
    }

    private void killMonster(SceneMap sceneMap, Player player, Monster monster) {
        sceneMap.removeEntity(monster);
        MonsterResource res = monsterResCache.getResource(monster.getConfigId());
        if (res.getReviveTime() > 0) {
            ThreadPool.TIMER.newTimeout(timeout -> sceneMap.tell(() -> {
                sceneManager.addMonster(sceneMap, monster.getConfigId(), monster.getSpawn());
            }), res.getReviveTime(), TimeUnit.SECONDS);
        }

        //掉落宝箱
        Position pos = monster.getPos();
        int[] dropCountArray = res.getDropCount();
        int dropCount = RandomUtil.nextIntBetween(dropCountArray[0], dropCountArray[1]);
        float dropRadius = 2f;//2米范围内
        for (int i = 0; i < dropCount; i++) {
            int[] goodsArray = RandomUtil.randomlySelectByWeight(res.getDropItem(), 2);

            Position dropPos = new Position();
            //角度
            float angle = RandomUtil.nextFloat(0f, 360f);
            //0.5m ~ 2m 随机半径
            float radius = RandomUtil.nextFloat(0.5f, dropRadius);
            //Math.toRadians(angle)：把度数转成弧度
            //Math.cos(...)：计算该弧度对应的余弦值（double)
            //* radius：把单位圆上的 x 分量按目标半径缩放，得到实际的 x 偏移量。  cos = 邻变 / 斜边， offsetX 即为邻变
            float offsetX = (float) (Math.cos(Math.toRadians(angle)) * radius);
            float offsetZ = (float) (Math.sin(Math.toRadians(angle)) * radius);
            dropPos.setY(0.5f);//带点高度高度
            dropPos.setX(pos.getX() + offsetX);
            dropPos.setZ(pos.getZ() + offsetZ);
            DropItem dropItem = new DropItem();
            dropItem.setId(uuidGenerator.nextId());
            dropItem.setAOIX((int) dropPos.getX());
            dropItem.setAOIY((int) dropPos.getZ());
            dropItem.setPos(dropPos);
            dropItem.setGoodsId(goodsArray[0]);
            dropItem.setGoodsNum(goodsArray[1]);

            sceneMap.addDropItem(dropItem);
        }
    }

    public void monsterAttack(SceneMap sceneMap, Monster monster, IPlayerEntity target) {
        int atk = monster.getAtk();
        int dmg = target.takeDamage(atk);
        DamageVo damageVo = new DamageVo();
        damageVo.setDamage(dmg);
        damageVo.setTargetId(target.getPlayerId());
        damageVo.setEntityType(EntityType.PLAYER.value());

        RespMonsterAttackPacket packet = new RespMonsterAttackPacket();
        packet.setMonsterId(monster.getId());
        packet.getDamageList().add(damageVo);
        walkRoundPlayer(sceneMap, monster, p -> {
            p.sendPacket(packet);
        });

        if (!target.isAlive()) {//死亡

        }
    }

    public void changeMap(Player player, SceneMap newMap, Runnable call) {
        SceneMap oldMap = sceneManager.getSceneMap(player);
        oldMap.tell(() -> {
            exitMap(oldMap, player);
            newMap.tell(() -> {
                enterMap(newMap, player);
                call.run();
            });
        });
    }

    public SceneMap getMap(int sceneId, int lineId) {
        return sceneManager.getSceneMap(sceneId, lineId);
    }

    public void pickUp(SceneMap sceneMap, Player player, int entityType, long targetId) {
        if (entityType == EntityType.DROP_ITEM.value()) {
            DropItem dropItem = sceneMap.getDropItems().get(targetId);
            if (dropItem == null) {
                return;
            }
            sceneMap.removeEntity(dropItem);

            int goodsId = dropItem.getGoodsId();
            int num = dropItem.getGoodsNum();
            player.tell(() -> {
                if (backPackService.isBackPackMax(player)) {
                    player.sendTip("背包已满");
                    return;
                }
                backPackService.addGoods(player, goodsId, num);
            });
        } else {
            return;
        }
        RespPickUpPacket packet = new RespPickUpPacket();
        packet.setEntityType(entityType);
        packet.setTargetId(targetId);
        player.sendPacket(packet);
    }
}
